﻿using System;
using System.Collections.Generic;
using Firefly.ExtensionMethods;
using Firefly.Framework.Components;
using Firefly.Framework.ContentManagement;
using Firefly.Framework.Input;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Firefly.Framework.Windows.Forms
{
    public class Control : DrawableComponent, IDrawableComponent, IPipelineSerializeable, IComparable<Control>
    {
        public event EventHandler<KeyMapEventArgs> KeyDown;
        public event EventHandler<KeyMapEventArgs> KeyUp;
        public event EventHandler<MouseButtonMapEventArgs> Clicked;
        public event EventHandler<MouseButtonMapEventArgs> DoubleClicked;
        public event EventHandler<MouseAxisMapEventArgs> MouseEnter;
        public event EventHandler<MouseAxisMapEventArgs> MouseExit;
        public event EventHandler<MouseButtonMapEventArgs> MouseUp;
        public event EventHandler<MouseButtonMapEventArgs> MouseDown;
        public event EventHandler<MouseScrollWheelMapEventArgs> MouseScroll;
        public event EventHandler SizeChanged;
        public event EventHandler TextChanged;

        private Color _color;
        private string _font;
        private Color _fontColor;
        private int _fontSize; 
        private Rectangle _position;
        private Vector4 _padding;
        private bool _wasMouseOver;
        private string _text;
        private DockStyle _dockStyle;

        [ContentSerializerIgnore]
        public new Control Parent { get; set; }
        public string Name { get; set; }
        [ContentSerializer(Optional = true)]
        public bool CanFocus { get; set; }
        [ContentSerializer(Optional = true)]
        public Color Color
        {
            get { return (InheritColor && Parent != null) ? Parent.Color : _color; }
            set { _color = value; }
        }
        [ContentSerializer(Optional = true)]
        public string Cursor { get; set; }
        [ContentSerializer(Optional = true)]
        public DockStyle DockStyle
        {
            get { return _dockStyle; }
            set 
            { 
                _dockStyle = value; 
                UpdateRectangles(); 
            }
        }
        [ContentSerializer(Optional = true)]
        public string Font
        {
            get { return (InheritFont && Parent != null) ? Parent.Font : _font; }
            set { _font = value; }
        }
        [ContentSerializer(Optional = true)]
        public Color FontColor
        {
            get { return (InheritFontColor && Parent != null) ? Parent.FontColor : _fontColor; }
            set { _fontColor = value; }
        }
        [ContentSerializer(Optional = true)]
        public int FontSize
        {
            get { return (InheritFontSize && Parent != null) ? Parent.FontSize : _fontSize; }
            set { _fontSize = value; }
        }
        [ContentSerializer(Optional = true)]
        public bool InheritColor { get; set; }
        [ContentSerializer(Optional = true)]
        public bool InheritFont { get; set; }
        [ContentSerializer(Optional = true)]
        public bool InheritFontColor { get; set; }
        [ContentSerializer(Optional = true)]
        public bool InheritFontSize { get; set; }
        [ContentSerializer(Optional = true)]
        public Vector4 Padding
        {
            get { return _padding; }
            set
            {
                _padding = value;
                UpdateRectangles();
            }
        }
        public Rectangle Position
        {
            get { return _position; }
            set
            {
                _position = value;
                UpdateRectangles();
            }
        }
        [ContentSerializer(Optional = true)]
        public virtual string TabNext { get; set; }
        [ContentSerializer(Optional = true)]
        public virtual string TabPrev { get; set; }
        [ContentSerializerIgnore]
        public bool WriteToContent { get; set; }
        [ContentSerializer(Optional = true)]
        public new List<Control> Children { get; set; }
        public bool HasFocus
        {
            get { return (WindowsService.Instance.Focused == this); }
        }
        [ContentSerializer(Optional = true)]
        public virtual string Text
        {
            get { return _text; }
            set
            {
                if (_text != value)
                {
                    _text = value;
                    OnTextChanged();                    
                }
            }
        }
        [ContentSerializer(Optional = true)]
        public Vector4 TextOffset { get; set; }
        [ContentSerializer(Optional = true)]
        public Vector4 ClientAreaOffset { get; set; }
        protected Rectangle DrawRectangle { get; set; }
        protected bool LayoutSuspended { get; set; }
        protected virtual Rectangle Interior { get; set; }
        protected int NaturalOrder { get; set; }

        public Control()
        {
            CanFocus = true;
            Children = new List<Control>(); 
            _color = Color.White;
            Cursor = "Default";
            DockStyle = DockStyle.None; 
            _font = "Default";
            _fontColor = Color.Black;
            _fontSize = 20;
            InheritColor = true;
            InheritFont = true;
            InheritFontColor = true;
            InheritFontSize = true;
            TabNext = string.Empty;
            TabPrev = string.Empty;
            WriteToContent = true;
            LayoutSuspended = true;
            Text = string.Empty;
        }

        public void BringToFront()
        {
            if (Parent != null)
            {
                Parent.BringToFront();
                Parent.NormalizeDrawOrder();
                DrawOrder = Parent.Children.Count;
            }
        }

        public void SendToBack()
        {
            if (Parent != null)
            {
                Parent.NormalizeDrawOrder();
                DrawOrder = 0;
            }
        }

        public void SuspendLayout()
        {
            LayoutSuspended = true;

            foreach (Control c in Children)
            {
                c.SuspendLayout();
            }
        }

        public void ResumeLayout()
        {
            LayoutSuspended = false;

            foreach (Control c in Children)
            {
                c.ResumeLayout();
            }

            Resize();
        }

        public Control FindComponent(Vector2 position)
        {
            if (!Visible)
            {
                return null;
            }

            Children.Sort(Control.DrawOrderDescendingComparer);
            Control control;
            foreach (Control c in Children)
            {
                control = c.FindComponent(position);

                if (control != null)
                {
                    Children.Sort();
                    return control;
                }
            }

            Children.Sort();

            if (DrawRectangle.Contains(position.ToPoint()))
            {
                return this;
            }
            else
            {
                return null;
            }
        }

        public Control FindComponent(string name)
        {
            if (name == null || name == string.Empty)
            {
                return null;
            }

            int i = name.IndexOf("\\");
            string firstPart;
            string secondPart;

            if (i == -1)
            {
                firstPart = name; secondPart = "";
            }
            else
            {
                firstPart = name.Substring(0, i);
                secondPart = name.Substring(i + 1);
            }

            foreach (Control c in Children)
            {
                if (firstPart == "..")
                {
                    if (secondPart == "")
                    {
                        return Parent;
                    }
                    else
                    {
                        return Parent.FindComponent(secondPart);
                    }
                }
                if (c.Name == firstPart)
                {
                    if (secondPart == "")
                    {
                        return c;
                    }
                    else
                    {
                        return c.FindComponent(secondPart);
                    }
                }
            }

            return null;
        }

        public void Focus()
        {
            BringToFront();
            WindowsService.Instance.Focused = this;
        }

        public void RaiseBubbleEvent(object source, BubbleEventArgs e)
        {
            OnBubbleEvent(source, e);
        }

        protected void OnBubbleEvent(object source, BubbleEventArgs e)
        {
            switch (e.EventType)
            {
                case BubbleEvent.KeyDown:
                    OnKeyDown(e.InputMap as KeyMap, e.EventArgs as KeyMapEventArgs);
                    break;
                case BubbleEvent.KeyUp:
                    OnKeyUp(e.InputMap as KeyMap, e.EventArgs as KeyMapEventArgs);
                    break;
                case BubbleEvent.Clicked:
                    OnClick(e.InputMap as MouseButtonMap, e.EventArgs as MouseButtonMapEventArgs);
                    break;
                case BubbleEvent.DoubleClicked:
                    OnDoubleClick(e.InputMap as MouseButtonMap, e.EventArgs as MouseButtonMapEventArgs);
                    break;
                case BubbleEvent.MouseUp:
                    OnMouseUp(e.InputMap as MouseButtonMap, e.EventArgs as MouseButtonMapEventArgs);
                    break;
                case BubbleEvent.MouseDown:
                    OnMouseDown(e.InputMap as MouseButtonMap, e.EventArgs as MouseButtonMapEventArgs);
                    break;
                case BubbleEvent.MouseMove:
                    OnMouseMove(e.InputMap as MouseAxisMap, e.EventArgs as MouseAxisMapEventArgs);
                    break;
                case BubbleEvent.MouseScroll:
                    OnMouseScroll(e.InputMap as MouseScrollWheelMap, e.EventArgs as MouseScrollWheelMapEventArgs);
                    break;
                case BubbleEvent.MouseExit:
                    OnMouseExit(e.InputMap as MouseAxisMap, e.EventArgs as MouseAxisMapEventArgs);
                    break;
                case BubbleEvent.MouseEnter:
                    OnMouseEnter(e.InputMap as MouseAxisMap, e.EventArgs as MouseAxisMapEventArgs);
                    break;
                case BubbleEvent.Resize:
                    Resize();
                    break;
                default:
                    break;
            }
        }

        protected virtual void Resize()
        {
            if (LayoutSuspended)
            {
                return;
            }

            Children.Sort(NaturalOrderComparer);
            UpdateRectangles();

            foreach (Control c in Children)
            {
                c.Resize();
            }
        }

        protected virtual void OnKeyDown(KeyMap map, KeyMapEventArgs e)
        {
            if (HasFocus)
            {
                switch (map.MappedButtons[0])
                {
                    case Keys.Tab:
                        if (InputService.Instance.KeyboardManager.IsButtonDown(Keys.LeftShift) ||
                            InputService.Instance.KeyboardManager.IsButtonDown(Keys.RightShift))
                        {
                            DoTabPrev();
                        }
                        else
                        {
                            DoTabNext();
                        }
                        break;
                    default:
                        if (Parent != null)
                        {
                            Parent.OnKeyDown(map, e);
                        }
                        break;
                }
            }

            NotifyKeyDown(e);
        }

        protected virtual void OnKeyUp(KeyMap map, KeyMapEventArgs e)
        {
            NotifyKeyUp(e);
        }

        protected virtual void OnClick(MouseButtonMap map, MouseButtonMapEventArgs e)
        {
            NotifyClick(e);
        }

        protected virtual void OnDoubleClick(MouseButtonMap map, MouseButtonMapEventArgs e)
        {
            NotifyDoubleClick(e);
        }

        protected virtual void OnMouseUp(MouseButtonMap map, MouseButtonMapEventArgs e)
        {
            NotifyMouseUp(e);
        }

        protected virtual void OnMouseDown(MouseButtonMap map, MouseButtonMapEventArgs e)
        {
            NotifyMouseDown(e);
        }

        protected virtual void OnMouseMove(MouseAxisMap map, MouseAxisMapEventArgs e)
        {
            bool mouseOver = IsMouseOver(e.CurrentPosition.ToPoint());

            if (mouseOver && !_wasMouseOver)
            {
                NotifyMouseEnter(e);
            }

            if (!mouseOver && _wasMouseOver)
            {
                NotifyMouseExit(e);
            }

            _wasMouseOver = mouseOver;
        }

        protected virtual void OnMouseScroll(MouseScrollWheelMap map, MouseScrollWheelMapEventArgs e)
        {
            NotifyMouseScroll(e);
        }

        protected virtual void OnMouseExit(MouseAxisMap map, MouseAxisMapEventArgs e)
        {
            NotifyMouseExit(e);
        }

        protected virtual void OnMouseEnter(MouseAxisMap map, MouseAxisMapEventArgs e)
        {
            NotifyMouseEnter(e);
        }

        protected virtual void OnSizeChanged()
        {
            NotifySizeChanged();
        }

        protected virtual void OnTextChanged()
        {
            NotifyTextChanged();
        }

        // TODO : Update this function to handle docking properly.
        protected virtual void UpdateRectangles()
        {
            if (Parent == null)
            {
                DrawRectangle = _position;
                return;
            }

            Control parent = Parent as Control;
            //Rectangle parentDock = parent.ClientArea;
            Rectangle clientArea = new Rectangle((int)(parent.DrawRectangle.X + parent.Padding.X + parent.ClientAreaOffset.X),
                (int)(parent.DrawRectangle.Y + parent.Padding.Y + parent.ClientAreaOffset.Y),
                (int)(parent.DrawRectangle.Width - parent.Padding.X - parent.Padding.Z - parent.ClientAreaOffset.X - parent.ClientAreaOffset.Z),
                (int)(parent.DrawRectangle.Height - parent.Padding.Y - parent.Padding.W - parent.ClientAreaOffset.Y - parent.ClientAreaOffset.W));

            switch (DockStyle)
            {
                case DockStyle.Top:
                    //DrawRectangle = new Rectangle(parentDock.X, 
                    //    parentDock.Y, 
                    //    parentDock.Width, 
                    //    _position.Height);
                    //parent.DockSpace = new Rectangle(parent.DockSpace.X,
                    //    parent.DockSpace.Y + _rectangle.Height,
                    //    parent.DockSpace.Width,
                    //    parent.DockSpace.Height - _rectangle.Height);
                    break;
                case DockStyle.Bottom:
                    //DrawRectangle = new Rectangle(parentDock.X,
                    //    parentDock.Bottom - _position.Height,
                    //    parentDock.Width,
                    //    _position.Height);
                    //parent.DockSpace = new Rectangle(parent.DockSpace.X,
                    //    parent.DockSpace.Y,
                    //    parent.DockSpace.Width,
                    //    parent.DockSpace.Height - _rectangle.Height);
                    break;
                case DockStyle.Left:
                    //DrawRectangle = new Rectangle(parentDock.X,
                    //    parentDock.Y,
                    //    _position.Width,
                    //    parentDock.Height);
                    //parent.DockSpace = new Rectangle(parent.DockSpace.X + _rectangle.Width,
                    //    parent.DockSpace.Y,
                    //    parent.DockSpace.Width,
                    //    parent.DockSpace.Height - _rectangle.Height);
                    break;
                case DockStyle.Right:
                    //DrawRectangle = new Rectangle(parentDock.Right - _position.Width,
                    //    parentDock.Y,
                    //    _position.Width,
                    //    parentDock.Height);
                    //parent.DockSpace = new Rectangle(parent.DockSpace.X,
                    //    parent.DockSpace.Y,
                    //    parent.DockSpace.Width - _rectangle.Width,
                    //    parent.DockSpace.Height);
                    break;
                case DockStyle.Fill:
                    //DrawRectangle = parentDock;
                    //parent.DockSpace = new Rectangle(parent.DockSpace.X,
                    //    parent.DockSpace.Y,
                    //    0,
                    //    0);
                    break;
                default:
                    DrawRectangle = new Rectangle(_position.X + clientArea.X,
                        _position.Y + clientArea.Y,
                        _position.Width <= clientArea.Width ? _position.Width : clientArea.Width,
                        _position.Height <= clientArea.Height ? _position.Height : clientArea.Height);
                    break;
            }

            //DockSpace = new Rectangle(DrawRectangle.X + _padding.X,
            //    DrawRectangle.Y + _padding.Y,
            //    DrawRectangle.Width - _padding.X - _padding.Width,
            //    DrawRectangle.Height - _padding.Y - _padding.Height);
            Interior = new Rectangle(DrawRectangle.X + (int)_padding.X,
                DrawRectangle.Y + (int)_padding.Y,
                DrawRectangle.Width - (int)_padding.X - (int)_padding.Z,
                DrawRectangle.Height - (int)_padding.Y - (int)_padding.W);
        }           

        protected void CheckChildNames()
        {
            List<string> names = new List<string>();

            foreach (Control c in Children)
            {
                if (names.Contains(c.Name))
                { 
                    throw new ApplicationException("'" + this.Name + "' has more than one child named '" + c.Name + "'."); 
                }

                names.Add(c.Name);
            }

            foreach (Control c in Children) 
            { 
                c.CheckChildNames(); 
            }
        }

        protected void NormalizeDrawOrder()
        {
            Children.Sort();
            int i = 0;

            foreach (Control c in Children)
            {
                c.DrawOrder = i;
                i++;
            }
        }

        protected virtual void DoTabNext()
        {
            if (Parent != null)
            {
                Control c = Parent.FindComponent(this.TabNext);

                if (c != null)
                {
                    c.Focus();
                }
            }
        }

        protected virtual void DoTabPrev()
        {
            if (Parent != null)
            {
                Control c = Parent.FindComponent(this.TabPrev);

                if (c != null)
                {
                    c.Focus();
                }
            }
        }

        protected virtual void NotifyKeyDown(KeyMapEventArgs e)
        {
            EventHandler<KeyMapEventArgs> handler = KeyDown;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected virtual void NotifyKeyUp(KeyMapEventArgs e)
        {
            EventHandler<KeyMapEventArgs> handler = KeyUp;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected void NotifyClick(MouseButtonMapEventArgs e)
        {
            EventHandler<MouseButtonMapEventArgs> handler = Clicked;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected void NotifyDoubleClick(MouseButtonMapEventArgs e)
        {
            EventHandler<MouseButtonMapEventArgs> handler = DoubleClicked;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected void NotifyMouseEnter(MouseAxisMapEventArgs e)
        {
            EventHandler<MouseAxisMapEventArgs> handler = MouseEnter;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected void NotifyMouseExit(MouseAxisMapEventArgs e)
        {
            EventHandler<MouseAxisMapEventArgs> handler = MouseExit;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected void NotifyMouseUp(MouseButtonMapEventArgs e)
        {
            EventHandler<MouseButtonMapEventArgs> handler = MouseUp;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected void NotifyMouseDown(MouseButtonMapEventArgs e)
        {
            EventHandler<MouseButtonMapEventArgs> handler = MouseDown;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected void NotifyMouseScroll(MouseScrollWheelMapEventArgs e)
        {
            EventHandler<MouseScrollWheelMapEventArgs> handler = MouseScroll;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected void NotifySizeChanged()
        {
            EventHandler handler = SizeChanged;

            if (handler != null)
            {
                handler(this, null);
            }
        }

        protected void NotifyTextChanged()
        {
            EventHandler handler = TextChanged;

            if (handler != null)
            {
                handler(this, null);
            }
        }

        protected bool IsMouseOverChild(Point position)
        {
            if (Visible)
            {
                foreach (Control c in Children)
                {
                    if (c.IsMouseOver(position))
                    {
                        return true;
                    }
                }

                return false;
            }

            return false;
        }

        protected bool IsMouseOver(Point position)
        {
            if (Visible)
            {
                if (IsMouseOverChild(position))
                {
                    return false;
                }

                return this.DrawRectangle.Contains(position);
            }

            return false;
        }        

        protected void DrawChildren(GameTime gameTime)
        {
            if (Visible)
            {
                Children.Sort(DrawOrderAscendingComparer);

                foreach (Control c in Children)
                { 
                    c.Draw(gameTime); 
                }
            }
        }

        #region IUpdateable Members

        public override void Update(GameTime gameTime)
        {
            if (Enabled)
            {
                foreach (Control c in Children)
                {
                    c.Update(gameTime);
                }
            }
        }

        #endregion

        #region IDrawable Members

        public override void Draw(GameTime gameTime)
        {
            WindowsService.Instance.DrawRectangle(DrawRectangle, Color);
            DrawChildren(gameTime);
        }

        #endregion

        #region IPipelineSerializeable Members

        public virtual void DeserializeContent(ContentReader input)
        {
            SuspendLayout();
            Name = input.ReadString();
            Position = input.ReadObject<Rectangle>();
            Padding = input.ReadObject<Vector4>();
            Color = input.ReadColor();
            Cursor = input.ReadString();
            Text = input.ReadString();
            TextOffset = input.ReadObject<Vector4>();
            ClientAreaOffset = input.ReadObject<Vector4>();
            Font = input.ReadString();
            FontColor = input.ReadColor();
            DockStyle = (DockStyle)input.ReadInt32();
            InheritColor = input.ReadBoolean();
            InheritFont = input.ReadBoolean();
            InheritFontColor = input.ReadBoolean();
            TabNext = input.ReadString();
            TabPrev = input.ReadString();
            Visible = input.ReadBoolean();
            DrawOrder = input.ReadInt32();
            Enabled = input.ReadBoolean();
            UpdateOrder = input.ReadInt32();
            Children.AddRange(input.ReadObject<List<Control>>());

            int i = 0;

            foreach (Control c in this.Children)
            {
                c.NaturalOrder = i; 
                i++; 
            }

            CheckChildNames();

            foreach (Control c in Children) 
            { 
                c.Parent = this; 
            }

            LoadContent();
        }

        #endregion

        #region IHasContent Members

        public override void LoadContent()
        {
        }

        public override void UnloadContent()
        {
            foreach (Control c in Children)
            {
                c.UnloadContent();
            }
        }

        #endregion
        
        #region IComparable Members

        public int CompareTo(Control other)
        {
            return DrawOrder.CompareTo(other.DrawOrder);
        }

        public static Comparison<Control> DrawOrderAscendingComparer =
            delegate(Control c1, Control c2)
            { 
                return c1.DrawOrder.CompareTo(c2.DrawOrder); 
            };

        public static Comparison<Control> DrawOrderDescendingComparer =
            delegate(Control c1, Control c2)
            { 
                return c2.DrawOrder.CompareTo(c1.DrawOrder); 
            };

        public static Comparison<Control> NaturalOrderComparer =
            delegate(Control c1, Control c2)
            {
                return c1.NaturalOrder.CompareTo(c2.NaturalOrder); 
            };

        #endregion

        #region IDisposable Members

        protected override void Dispose(bool disposing)
        {
            if (!Disposed && disposing)
            {
                UnloadContent();
            }

            Disposed = true;
        }

        ~Control()
        {
            Dispose(false);
        }

        #endregion
    }
}
